namespace C4F.DevKit.Bluetooth
{
    namespace ServiceAndDeviceDiscovery
    {
        #region Using Directives
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Runtime.InteropServices;
        using System.Net.Sockets;
        using Resources;
        #endregion

        /// <summary>
        /// Represents a service which can be advertisized on local device.       
        /// </summary>
        public abstract class LocalService : Service
        {
            #region Protected Members

            /// <summary>
            /// Socket used for connecting to this service.
            /// </summary>
            protected Socket socket;

            /// <summary>
            /// Structure containing service specific information
            /// </summary>
            protected QuerySet wsaq;

            #endregion          
            
            #region Public Functions

            /// <summary>
            /// Initializes a Local Service object.
            /// </summary>
            public LocalService()
                : base()
            {
            }

            /// <summary>
            /// Advertisizes and Registers the service on local device.
            /// </summary>        
            /// <param name="type">Type of the service to be advertisized.
            /// </param>
            /// <param name="name">Name of the service to be advertisized.
            /// </param>
            /// <param name="description">Description of the servie to be 
            /// advertisized.</param>
            /// <returns>ServiceHandle object which allows the end-user to  
            /// control connections to this service. </returns>
            /// <exception cref="ServiceRegistrationException">Thrown when 
            /// an error occurs while registering the service.</exception>
            /// <exception cref="ArgumentNullException">Thrown when 
            /// a null reference (not set or empty) is passed 
            /// to a method that does not accept it as a valid argument.
            /// </exception>            
            public virtual ServiceHandle Register(ServiceType type, string name,
                                          string description)
            {
                //Verify if type of service is valid.
                if (type == null)
                    throw new ArgumentNullException
                                (SDPMessages.ErrorMsgNullServiceType);

                //Verify if name of service is valid.
                if (string.IsNullOrEmpty(name))
                    throw new ArgumentNullException
                                 (SDPMessages.ErrorMsgNullName);

                //Verify if description of service is valid.
                if (string.IsNullOrEmpty(description))
                    throw new ArgumentNullException
                                (SDPMessages.ErrorMsgNullDescription);



                try
                {

                    #region Socket

                    // create the socket 
                    this.socket = new Socket((AddressFamily)32,
                                        SocketType.Stream,
                                        ProtocolType.Ggp);


                    BluetoothSocketAddress localAddr =
                                    new BluetoothSocketAddress();

                    // Create a Bluetooth endpoint 
                    BluetoothEndPoint btep =
                            new BluetoothEndPoint(localAddr, type.ServiceGuid);

                    // bind the socket to the endpoint
                    this.socket.Bind(btep);

                    // extract port number
                    int port = ((BluetoothEndPoint)this.socket.LocalEndPoint).Port;


                    #endregion

                    // Create a Service Record
                    byte[] sdpRecord = GenerateSDPRecord(type,
                                                         port,
                                                         name,
                                                         description);


                    #region setup BTH_SET_SERVICE Structure

                    // Setup the BthSetService structure
                    int sdpVersion = BluetoothHelper.BTH_SDP_VERSION;

                    BthSetService bthService = new BthSetService();

                    IntPtr hSdpVersion =
                        Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)));

                    Marshal.WriteInt32(hSdpVersion, sdpVersion);

                    IntPtr hService =
                        Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));

                    Marshal.WriteIntPtr(hService, IntPtr.Zero);

                    bthService.SdpVersion = hSdpVersion;
                    bthService.RecordHandle = hService;
                    bthService.RecordLength = (uint)sdpRecord.Length;
                    bthService.Record = sdpRecord;

                    #endregion

                    #region setup BLOB structure
                    //Setup the blob structure which contains a pointer to the
                    //BthSetService structure.
                    IntPtr pService = Marshal.AllocHGlobal
                                           (Marshal.SizeOf(bthService));

                    Marshal.StructureToPtr(bthService, pService, false);

                    Blob blob = new Blob();
                    blob.Size = (uint)Marshal.SizeOf(bthService);
                    blob.BlobData = pService;
                    #endregion

                    #region QUERYSET
                    //Setup the queryset structure which contains information 
                    //specific to the service
                    this.wsaq = new QuerySet();
                    this.wsaq.Size = Marshal.SizeOf(this.wsaq);
                    this.wsaq.NameSpace = BluetoothHelper.NS_BTH;

                    IntPtr pBlob = Marshal.AllocHGlobal(Marshal.SizeOf(blob));
                    Marshal.StructureToPtr(blob, pBlob, false);
                    this.wsaq.Blob = pBlob;
                    IntPtr ptrServiceClassId =
                        Marshal.AllocHGlobal(Marshal.SizeOf(type.ServiceGuid));

                    Marshal.StructureToPtr(type.ServiceGuid, ptrServiceClassId, true);
                    this.wsaq.ServiceClassId = ptrServiceClassId;

                    #endregion

                    //Register the service
                    int result = 0;
                    result = BluetoothHelper.WSASetService(this.wsaq,
                                                     WsaSetServiceOp.Register,
                                                     0);

                    if (result != 0)
                    {
                        BluetoothHelper.GetError();
                    }
                    else
                    {
                        this.description = description;
                        this.id.ServiceIdType = ServiceIdType.TypeNil;
                        this.id.ServiceIdUnion = new ServiceIdUnion();
                        this.name = name;
                        this.serviceRecord = sdpRecord;
                        this.type = type;
                    }
                    //SerialPort Profile can service only one client at a time.
                    if (type.ServiceGuid == BluetoothHelper.SERIAL_PORT_PROFILE)
                        this.socket.Listen(1);
                    else
                        //For the rest accept more than one connection.
                        this.socket.Listen(100);

                    return new ServiceHandle(this.socket);
                }
                catch (WinsockException winSockEx)
                {
                    ServiceRegistrationException servEx =
                        new ServiceRegistrationException
                        (SDPMessages.ErrorMsgServiceRegistration
                        + winSockEx.Message);

                    throw servEx;
                }
                catch (Exception ex)
                {
                    ServiceRegistrationException servEx =
                        new ServiceRegistrationException
                        (SDPMessages.ErrorMsgServiceRegistration
                        + ex.Message);

                    throw servEx;
                }
            }

            /// <summary>
            /// Stops advertizing the service
            /// </summary>      
            /// <exception cref="ServiceRegistrationException">Thrown when 
            /// an error occurs while unregistering the service.</exception>
            public virtual void UnRegister()
            {
                try
                {

                    if (this.wsaq != null)
                    {
                        if (this.socket != null)
                            this.socket.Close();

                        //Un-registers the service.
                        int result =
                            BluetoothHelper.WSASetService(this.wsaq, WsaSetServiceOp.Delete, 0);

                        if (result != 0)
                        {
                            BluetoothHelper.GetError();
                        }
                    }
                    else
                        throw new ServiceRegistrationException
                            (SDPMessages.ErrorMsgServiceNotRegistered);
                }
                catch (Exception ex)
                {

                    if (ex is ServiceRegistrationException)
                        throw ex;

                    if (ex.InnerException is ServiceRegistrationException)
                        throw ex.InnerException;
                   
                    ServiceRegistrationException servEx =
                        new ServiceRegistrationException(
                        SDPMessages.ErrorMsgServiceNotRegistered
                        + ex.Message);
                    throw servEx;
                }
                finally
                {
                    this.wsaq = null;
                }
            }


            /// <summary>
            /// Creates a service record which describes the service. 
            /// The arguments and their values are used in creating the service 
            /// record.
            /// </summary>
            /// <param name="type">Type of the service.</param>
            /// <param name="port">Port for the service.</param>
            /// <param name="name">Name of the service.</param>
            /// <param name="description">Description of the service.</param>
            /// <returns>Returns the service record of the service.</returns>
            /// <exception cref="ServiceRegistrationException">Thrown when 
            /// an error occurs while generating Service Record.</exception>  
            /// <exception cref="ArgumentNullException">Thrown when 
            /// when a null reference (not set or empty) is passed 
            /// to a method that does not accept it as a valid argument.
            /// </exception>           
            public virtual byte[] GenerateSDPRecord(ServiceType type,
                                                    int port,
                                                    string name,
                                                    string description)
            {
                //Verify if type is valid.
                if (type == null)
                    throw new ArgumentNullException
                                (SDPMessages.ErrorMsgNullServiceType);

                //Verify if name is valid.
                if (string.IsNullOrEmpty(name))
                    throw new ArgumentNullException
                                            (SDPMessages.ErrorMsgNullName);

                //Verify if description is valid.
                if (string.IsNullOrEmpty(description))
                    throw new ArgumentNullException
                            (SDPMessages.ErrorMsgNullDescription);


                try
                {

                    System.Text.ASCIIEncoding encoding =
                                        new System.Text.ASCIIEncoding();


                    byte[] serviceNameArray = null;
                    byte[] serviceDescArray = null;


                    serviceNameArray = encoding.GetBytes(name);
                    serviceDescArray = encoding.GetBytes(description);

                    //service record for the service.
                    byte[] sdpRecord =
                        new byte[] { 
                            0x35, 0x7C, 0x09, 0x00, 0x01, 0x35, 0x11, 0x1C,                        
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x09, 0x00, 0x04, 0x35, 0x0C, 0x35, 0x03, 0x19,
                            0x01, 0x00, 0x35, 0x05, 0x19, 0x00, 0x03, 0x08,
                            0x00, 0x09, 0x01, 0x00, 0x25, 0x19, 0x00, 0x00, 
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
                            0x01, 0x01, 0x25, 0x32, 0x00, 0x00, 0x00, 0x00,  
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

                    // set sdp record length
                    sdpRecord[1] = Convert.ToByte(sdpRecord.Length - 2);

                    // copy in the guid of the type of service in network byte order
                    byte[] guidArray = type.ServiceGuid.ToByteArray();

                    sdpRecord[8] = guidArray[3];
                    sdpRecord[9] = guidArray[2];
                    sdpRecord[10] = guidArray[1];
                    sdpRecord[11] = guidArray[0];

                    sdpRecord[12] = guidArray[5];
                    sdpRecord[13] = guidArray[4];

                    sdpRecord[14] = guidArray[7];
                    sdpRecord[15] = guidArray[6];

                    Array.Copy(guidArray, 8, sdpRecord, 16, 8);

                    sdpRecord[40] = Convert.ToByte(port);


                    int serviceNameLength = 0;
                    int serviceDescLength = 0;

                    if (serviceNameArray.Length <= 25)
                        serviceNameLength = serviceNameArray.Length;
                    else
                        serviceNameLength = 25;

                    if (serviceDescArray.Length <= 50)
                        serviceDescLength = serviceDescArray.Length;
                    else
                        serviceDescLength = 50;

                    //copy the name of service to the sdp record.
                    for (int i = 0; i < serviceNameLength; i++)
                        sdpRecord[46 + i] = serviceNameArray[i];

                    //copy the description of service to the sdp record.
                    for (int i = 0; i < serviceDescLength; i++)
                        sdpRecord[76 + i] = serviceDescArray[i];
                    return sdpRecord;

                }
                catch
                {
                    ServiceRegistrationException servEx =
                     new ServiceRegistrationException
                              (SDPMessages.ErrorMsgGenSdpRecord);

                    throw servEx;
                }
            }
            #endregion
        }

    }
}